/**
 * RetryOnExceptionBeanPostProcessorSpec.java
 * 
 * Created on Jul 4, 2008 at 7:18:33 PM
 */
package org.lego4j;

import static java.lang.String.format;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;
import static org.lego4j.matchers.Lego4jMatchers.instanceOf;
import static org.lego4j.matchers.Lego4jMatchers.throwsException;

import java.util.concurrent.Callable;

import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.support.BeanDefinitionValidationException;

/**
 * Specification for {@link Lego4jBeanPostProcessor}.
 *
 * @author <a href="alexguev@gmail.com">Alexei Guevara</>
 * @version $Revision:$ updated on $Date:$ by $Author:$
 */
public class RetryOnExceptionBeanPostProcessorSpec {
    
    private static final String MUST_HAVE_EX_PATTERN = "The throws clause must include one class that is equals or is a super class of %s";
    private static final String MUST_HAVE_EX_IN_ANNOTATION_ATTRIBUTE = "The e attribute must have at least one exception";
    
	private Lego4jBeanPostProcessor myBeanPostProcessor;
    
    @Before
    public void createBeanPostProcessor() throws Exception {
        this.myBeanPostProcessor = new Lego4jBeanPostProcessor();
        this.myBeanPostProcessor.afterPropertiesSet();
    }
    
    @Test
    public void shouldFailWhenExInAnnotationIsCheckedAndThereIsNoThrowsClause() {
        assertValidationShouldThrowException( 
                ClassWithMethodWithCheckedExInAnnotationThatHasNoThrowsClause.class,
                format(MUST_HAVE_EX_PATTERN, CheckedExA.class.getName()));
    }
    
    @Test
    public void shouldFailWhenExInAnnotationIsRuntimeAndThereIsNoThrowsClause() {
        assertValidationShouldThrowException( 
                ClassWithMethodWithRuntimeExInAnnotationThatHasNoThrowsClause.class,
                format(MUST_HAVE_EX_PATTERN, RuntimeExA.class.getName()));
    }

    @Test
    public void shouldFailWhenClassWithMethodWithCheckedExInAnnotationThatIsNotInThrowsClauseOrIsSubClassOfExInThrowsClause() {
        assertValidationShouldThrowException( 
                ClassWithMethodWithCheckedExInAnnotationThatIsNotInThrowsClauseOrIsSubClassOfExInThrowsClause.class,
                format(MUST_HAVE_EX_PATTERN, CheckedExA.class.getName()));
    }
    
    @Test
    public void shouldFailWhenClassWithMethodWithRuntimeExInAnnotationThatIsNotInThrowsClauseOrIsSubClassOfExInThrowsClause() {
        assertValidationShouldThrowException( 
                ClassWithMethodWithRuntimeExInAnnotationThatIsNotInThrowsClauseOrIsSubClassOfExInThrowsClause.class,
                format(MUST_HAVE_EX_PATTERN, RuntimeExA.class.getName()));
    }

    @Test
    public void shouldFailWhenClassWithMethodWithBlankExListInAnnotation() {
        assertValidationShouldThrowException( 
        		ClassWithMethodWithBlankExListInAnnotation.class,
        		MUST_HAVE_EX_IN_ANNOTATION_ATTRIBUTE);
    }
    
    @Test
    public void shouldNotFail() {
        this.myBeanPostProcessor.postProcessAfterInitialization( new ClassWithValidMethodDeclarations(), "class" );        
    }
    
    private void assertValidationShouldThrowException(
            final Class<?> theClass,
            String exceptionMessage) {
    	
        assertThat( 
                new Callable<Object>() {
                    public Object call() throws Exception {
                        RetryOnExceptionBeanPostProcessorSpec.this.myBeanPostProcessor.postProcessAfterInitialization( theClass.newInstance(), "class" );
                        return null;
                }},
                throwsException(instanceOf(BeanDefinitionValidationException.class))
                    .withMessage(equalTo(exceptionMessage))
                    .withCause(nullValue(Throwable.class)));
        
    }

    static class ClassWithMethodWithCheckedExInAnnotationThatHasNoThrowsClause {
        @RetryOnException( e=CheckedExA.class )
        public void method() {
        }
    }
    
    static class ClassWithMethodWithRuntimeExInAnnotationThatHasNoThrowsClause {
        @RetryOnException( e=RuntimeExA.class )
        public void method() {
        }
    }
    
    static class ClassWithMethodWithCheckedExInAnnotationThatIsNotInThrowsClauseOrIsSubClassOfExInThrowsClause {
        @RetryOnException( e=CheckedExA.class )
        public void method() throws CheckedExB {
        }
    }

    static class ClassWithMethodWithRuntimeExInAnnotationThatIsNotInThrowsClauseOrIsSubClassOfExInThrowsClause {
        @RetryOnException( e=RuntimeExA.class )
        public void method() throws RuntimeExB {
        }
    }

    static class ClassWithValidMethodDeclarations {

        @RetryOnException( e=CheckedExA.class )
        public void method2() throws Exception {
        }

        @RetryOnException( e=RuntimeExA.class )
        public void method1() throws RuntimeException {
        }
        
        @RetryOnException( e=CheckedExB.class )
        public void method3() throws CheckedExB {
        }

        @RetryOnException( e=RuntimeExB.class )
        public void method4() throws RuntimeExB {
        }
        
    }

    static class ClassWithMethodWithBlankExListInAnnotation {
        @RetryOnException(e={})
        public void method() throws Exception {
        }
    }
    
    static class CheckedExA extends Exception {
        private static final long serialVersionUID = -6686044059136500816L;
    }
    
    static class CheckedExB extends Exception {
        private static final long serialVersionUID = -6686044059136500816L;
    }

    static class RuntimeExA extends RuntimeException {
        private static final long serialVersionUID = 6026184427947212566L;
    }
    
    static class RuntimeExB extends RuntimeException {
        private static final long serialVersionUID = 6026184427947212566L;
    }
    
}
